/**
 * Anderground project - Subway maps processor application.
 * Copyright (C) 2008-2009 Eldar Abusalimov, Ilya Shurigyn
 * 
 * This file is part of Anderground.
 * 
 * Anderground is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * Anderground is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Anderground.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.anderground.core;

import java.util.Stack;

import org.anderground.internal.AbstractLogger;
import org.anderground.internal.SilentInstanceCreator;

/**
 * Simple suitable Logger class.
 * 
 * @author Ilya Shurigyn (honorik@gmail.com)
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 */
public final class Logger {

	private static final String IMPL_CLASS_NAME = "org.anderground.internal.LoggerImpl";

	/** logging on/off switcher */
	private static boolean logsEnabled = true;

	/** Logger implementation instance */
	private static AbstractLogger logger;

	public final static String TAG;

	public static final String EXCEPTION = "exception";

	/**
	 * stack structure to organize time profiling
	 */
	private static Stack<Long> profilerTimeStack = new Stack<Long>();

	/**
	 * pseudo-tree organization
	 */
	private static String treePrefix = "";

	private static final String TREE_ELEMENT = " |";

	/**
	 * write an exception to log with EXCEPTION tag
	 * 
	 * @param e
	 *            the exception, a message of which will be logged
	 * @return
	 */
	public static long log(Throwable e) {
		logger.e(TAG, EXCEPTION, e);
		return log("(" + e.getClass().getSimpleName() + ") " + e.getMessage());
	}

	/**
	 * logging message saving logging time;
	 * 
	 * @param message
	 *            - message to log
	 * @return log record time
	 */
	public static long log(String message) {
		return log(message, false);
	}

	/**
	 * logging message saving logging time;
	 * 
	 * @param message
	 *            - message to log
	 * @param isProfilerTimer
	 * @return log record time
	 */
	private static long log(String message, boolean isProfilerTimer) {
		if (!logsEnabled) {
			return 0;
		}

		long millis = 0;

		String prefix = treePrefix;
		if (isProfilerTimer) {
			prefix += " ";
		} else {
			prefix += "-";
			millis = System.currentTimeMillis();
		}

		logger.d(TAG, prefix + message);

		// TODO
		return millis;
	}

	/**
	 * save current time for profiling
	 * 
	 * @param message
	 */
	public static void logBegin(String message) {
		if (!logsEnabled) {
			return;
		}

		profilerTimeStack.push(log(message));
		treePrefix += TREE_ELEMENT;
	}

	/**
	 * retrieves last saved time and logs the difference
	 * 
	 * @param message
	 */
	public static void logEnd(String message) {
		if (!logsEnabled) {
			return;
		}

		if (profilerTimeStack.isEmpty()) {
			return;
		}

		long time = profilerTimeStack.pop();
		long timeInterval = Math.abs(System.currentTimeMillis() - time);
		int depth = profilerTimeStack.size();
		treePrefix = "";
		for (int i = 0; i < depth; i++) {
			treePrefix += TREE_ELEMENT;
		}
		log(timeInterval + " millis (" + message + ")", true);
		log("", true);
	}

	/**
	 * Calculate time between two log records and write it to log
	 * 
	 * @param startRec
	 * @param finishRec
	 * @return time between two records
	 */
	public static long writeTimeInterval(long startRec, long finishRec) {
		long timeInterval = Math.abs(finishRec - startRec);
		log("    Processing time: " + ((float) timeInterval) / 1000
				+ " seconds");
		return timeInterval;
	}

	/**
	 * Calculate time elapsed after the record added and write it to log
	 * 
	 * @param startRec
	 * @return time has elapsed after the record added
	 */
	public static long writeTimeInterval(long startRec) {
		return writeTimeInterval(startRec, System.currentTimeMillis());
	}

	/**
	 * @param msg
	 * @param tr
	 */
	public static void d(String msg, Throwable tr) {
		logger.d(TAG, msg, tr);
	}

	/**
	 * @param tr
	 */
	public static void d(Throwable tr) {
		logger.d(TAG, EXCEPTION, tr);
	}

	/**
	 * @param msg
	 */
	public static void d(String msg) {
		logger.d(TAG, msg);
	}

	/**
	 * @param msg
	 * @param tr
	 */
	public static void v(String msg, Throwable tr) {
		logger.v(TAG, msg, tr);
	}

	/**
	 * @param tr
	 */
	public static void v(Throwable tr) {
		logger.v(TAG, EXCEPTION, tr);
	}

	/**
	 * @param msg
	 */
	public static void v(String msg) {
		logger.v(TAG, msg);
	}

	/**
	 * @param msg
	 * @param tr
	 */
	public static void e(String msg, Throwable tr) {
		logger.e(TAG, msg, tr);
	}

	/**
	 * @param tr
	 */
	public static void e(Throwable tr) {
		logger.e(TAG, EXCEPTION, tr);
	}

	/**
	 * @param msg
	 */
	public static void e(String msg) {
		logger.e(TAG, msg);
	}

	/**
	 * @param msg
	 * @param tr
	 */
	public static void i(String msg, Throwable tr) {
		logger.i(TAG, msg, tr);
	}

	/**
	 * @param tr
	 */
	public static void i(Throwable tr) {
		logger.i(TAG, EXCEPTION, tr);
	}

	/**
	 * @param msg
	 */
	public static void i(String msg) {
		logger.i(TAG, msg);
	}

	/**
	 * @param msg
	 * @param tr
	 */
	public static void w(String msg, Throwable tr) {
		logger.w(TAG, msg, tr);
	}

	/**
	 * @param tr
	 */
	public static void w(Throwable tr) {
		logger.w(TAG, EXCEPTION, tr);
	}

	/**
	 * @param msg
	 */
	public static void w(String msg) {
		logger.w(TAG, msg);
	}

	static {

		try {
			logger = SilentInstanceCreator.createInstanceOf(IMPL_CLASS_NAME);
		} catch (Throwable e) {
			logger = new DefaultLoggerImpl();
			logger.w("DefaultLoggerImpl", "LoggerImpl initialization FAILED");
			logger.w("DefaultLoggerImpl", "using DefaultLoggerImpl");
			logger.w("DefaultLoggerImpl", " ");
		}

		TAG = logger.getTag();
		i("Anderground project");
		i("Copyright (C) 2008  Eldar Abusalimov, Ilya Shurigyn");
		i(" ");
		i("This program comes with ABSOLUTELY NO WARRANTY;");
		i("This is free software, and you are welcome to redistribute it");
		i("under certain conditions;");
		i("See the GNU General Public License for more details.");
		i(" ");

	}

	/**
	 * @author Eldar Abusalimov
	 */
	private static class DefaultLoggerImpl implements AbstractLogger {

		private static final String TAG = "AndergroundCoreLogger";
		private static final String D = " (DD) ";
		private static final String V = "\t";
		private static final String I = " (II) ";
		private static final String W = " (WW) ";
		private static final String E = " (EE) ";

		@Override
		public String getTag() {
			return TAG;
		}

		@Override
		public void d(String tag, String msg, Throwable tr) {
			System.out.println(D + msg);
			tr.printStackTrace();
		}

		@Override
		public void d(String tag, String msg) {
			System.out.println(D + msg);
		}

		@Override
		public void v(String tag, String msg, Throwable tr) {
			System.out.println(V + msg);
			tr.printStackTrace();
		}

		@Override
		public void v(String tag, String msg) {
			System.out.println(V + msg);
		}

		@Override
		public void e(String tag, String msg, Throwable tr) {
			System.out.println(E + msg);
			tr.printStackTrace();
		}

		@Override
		public void e(String tag, String msg) {
			System.out.println(E + msg);
		}

		@Override
		public void i(String tag, String msg, Throwable tr) {
			System.out.println(I + msg);
			tr.printStackTrace();
		}

		@Override
		public void i(String tag, String msg) {
			System.out.println(I + msg);
		}

		@Override
		public void w(String tag, String msg, Throwable tr) {
			System.out.println(W + msg);
			tr.printStackTrace();
		}

		@Override
		public void w(String tag, String msg) {
			System.out.println(W + msg);
		}

	}

}
